﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.model.Instance;
using l1cs.server.serverpackets;
using l1cs.server.datatables;

namespace l1cs.server.model
{
    /**
     * 加速器の使用をチェックするクラス。
     */
    public class AcceleratorChecker
    {
        private static Logger _log = Logger.getLogger( typeof( AcceleratorChecker ).FullName );

        private L1PcInstance _pc;

        private int _injusticeCount;

        private int _justiceCount;

        private static int INJUSTICE_COUNT_LIMIT = Config.INJUSTICE_COUNT;

        private static int JUSTICE_COUNT_LIMIT = Config.JUSTICE_COUNT;

        // 実際には移動と攻撃のパケット間隔はsprの理論値より5%ほど遅い。
        // それを考慮して-5としている。
        private static double CHECK_STRICTNESS = ( Config.CHECK_STRICTNESS - 5 ) / 100D;

        private static double HASTE_RATE = 0.745;

        private static double WAFFLE_RATE = 0.874;

        private Dictionary<ACT_TYPE, long> _actTimers = new Dictionary<ACT_TYPE, long>();

        private Dictionary<ACT_TYPE, long> _checkTimers = new Dictionary<ACT_TYPE, long>();

        public enum ACT_TYPE
        {
            MOVE, ATTACK, SPELL_DIR, SPELL_NODIR
        }

        // チェックの結果
        public const int R_OK = 0;

        public const int R_DETECTED = 1;

        public const int R_DISCONNECTED = 2;

        public AcceleratorChecker() { }

        public AcceleratorChecker( L1PcInstance pc )
        {
            _pc = pc;
            _injusticeCount = 0;
            _justiceCount = 0;
            long now = DateTime.Now.Ticks / 10000;
            foreach ( ACT_TYPE each in Enum.GetValues( typeof( ACT_TYPE ) ) )
            {
                _actTimers.Add( each, now );
                _checkTimers.Add( each, now );
            }
        }

        /**
         * アクションの間隔が不正でないかチェックし、適宜処理を行う。
         * 
         * @param type -
         *            チェックするアクションのタイプ
         * @return 問題がなかった場合は0、不正であった場合は1、不正動作が一定回数に達した ためプレイヤーを切断した場合は2を返す。
         */
        public int checkInterval( ACT_TYPE type )
        {
            int result = R_OK;
            long now = DateTime.Now.Ticks / 10000;
            long interval = now - _actTimers[type];
            int rightInterval = getRightInterval( type );

            interval = (long)( interval * CHECK_STRICTNESS );

            if ( 0 < interval && interval < rightInterval )
            {
                _injusticeCount++;
                _justiceCount = 0;
                if ( _injusticeCount >= INJUSTICE_COUNT_LIMIT )
                {
                    doDisconnect();
                    return R_DISCONNECTED;
                }
                result = R_DETECTED;
            }
            else if ( interval >= rightInterval )
            {
                _justiceCount++;
                if ( _justiceCount >= JUSTICE_COUNT_LIMIT )
                {
                    _injusticeCount = 0;
                    _justiceCount = 0;
                }
            }

            //検証用
            //double rate = (double) interval / rightInterval;
            //System.out.println(String.format("%s: %d / %d = %.2f (o-%d x-%d)",
            //type.toString(), interval, rightInterval, rate,
            //_justiceCount, _injusticeCount));

            _actTimers.Add( type, now );
            return result;
        }

        private void doDisconnect()
        {
            if ( !_pc.isGm() )
            {
                _pc.sendPackets( new S_ServerMessage( 945 ) ); // 違法プログラムが見つかったので、終了します。
                _pc.sendPackets( new S_Disconnect() );
                _log.info( String.Format(
                        "加速器検知のため%sを強制切断しました。", _pc.getName() ) );
            }
            else
            {
                // GMは切断しない
                _pc.sendPackets( new S_SystemMessage(
                        "加速器検知にひっかかっています。" ) );
                _injusticeCount = 0;
            }
        }

        /**
         * PCの状態から指定された種類のアクションの正しいインターバル(ms)を計算し、返す。
         * 
         * @param type -
         *            アクションの種類
         * @param _pc -
         *            調べるPC
         * @return 正しいインターバル(ms)
         */
        private int getRightInterval( ACT_TYPE type )
        {
            int interval;

            switch ( type )
            {
                case ACT_TYPE.ATTACK:
                    interval = SprTable.getInstance().getAttackSpeed( _pc.getTempCharGfx(), _pc.getCurrentWeapon() + 1 );
                    break;
                case ACT_TYPE.MOVE:
                    interval = SprTable.getInstance().getMoveSpeed( _pc.getTempCharGfx(), _pc.getCurrentWeapon() );
                    break;
                case ACT_TYPE.SPELL_DIR:
                    interval = SprTable.getInstance().getDirSpellSpeed( _pc.getTempCharGfx() );
                    break;
                case ACT_TYPE.SPELL_NODIR:
                    interval = SprTable.getInstance().getNodirSpellSpeed( _pc.getTempCharGfx() );
                    break;
                default:
                    return 0;
            }

            if ( _pc.isHaste() )
            {
                interval = (int)( interval * HASTE_RATE );
            }
            if ( type.Equals( ACT_TYPE.MOVE ) && _pc.isFastMovable() )
            {
                interval = (int)( interval * HASTE_RATE );
            }
            if ( type.Equals( ACT_TYPE.ATTACK ) && _pc.isFastAttackable() )
            {
                interval = (int)( interval * HASTE_RATE );
            }
            if ( _pc.isBrave() )
            {
                interval = (int)( interval * HASTE_RATE );
            }
            if ( _pc.isElfBrave() )
            {
                interval = (int)( interval * WAFFLE_RATE );
            }
            return interval;
        }
    }
}
